/**
 * Copyright 2011 Brigham Young University
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package edu.byu.nlp.classify;

import org.apache.commons.math3.linear.RealMatrix;
import org.apache.commons.math3.linear.RealVector;

import com.google.common.base.Preconditions;

import edu.byu.nlp.data.FeatureMatrix;
import edu.byu.nlp.math.RealVectors;

/**
 * A linear model which produces scores by multiplying the feature matrix by the weight vector.
 * 
 * @author rah67
 *
 */
public class LinearModel implements Classifier {
	
	private final RealVector weights;
	private final int numClasses;
	
	/**
	 * Constructs a linear model for the given weights and number of classes. This class assumes ownership of the
	 * weights, which generally shouldn't be changed after this instance is instantiated.
	 * 
	 * @throws NullPointerException if weights is null
	 * @throws IllegalArgumentException if numClasses < 0
	 */
	public LinearModel(RealVector weights, int numClasses) {
		Preconditions.checkNotNull(weights);
		Preconditions.checkArgument(numClasses >= 0);
		
		this.weights = weights;
		this.numClasses = numClasses;
	}
	
	/**
	 * Computes the scores for each class given the input matrix.
	 * 
	 * @throws NullPointerException if input is null
	 * @throws IllegalArgumentException if number of rows in input is less than the number of classes
	 */
	// TODO: it would be nice to have an interface void scores(input, output) to avoid unnecessary
	// memory allocation.
	public RealVector scores(RealMatrix input) {
		Preconditions.checkNotNull(input);
		Preconditions.checkArgument(input.getRowDimension() >= numClasses);
		
		return input.operate(weights);
	}
	
	/**
	 * Adds the scores for each class given the input matrix to init.
	 * 
	 * @throws NullPointerException if either input or init are null
	 * @throws IllegalArgumentException if number of rows in input is less than the number of classes
	 */
	public void addScoresToExisting(RealMatrix input, RealVector init) {
		Preconditions.checkNotNull(init);
		Preconditions.checkArgument(init.getDimension() == numClasses);

		// input checked in scores
		// TODO(rah67): requiring a copy is ridiculous. There's got to be a better way, else we'll have to
		// use our own classes.
		RealVectors.addToSelf(init, scores(input));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int classify(FeatureMatrix instance) {
		return scores(instance).getMinIndex();
	}
}
